/* 
 * Copyright (C) 2006 Sasa Coh <sasacoh@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

using System.Collections.Generic;

namespace UIFramework 
{

  /**
   * @class CPage
   *
   * @brief The basic composite class. Menu browsing is based on pages.
   *
   * @author Sasa Coh
   */
  public class CPage : CComposite 
  {

    protected string mText = "";				 //!< name of page menu
    protected bool _forgetable = false;    //!< flag defines history handling for this page. If true page is not stored in history.
    protected bool _clearHistory = false;  //!< clear previous page history

    //////////////////////////////////////////////////////////////////////////////////

    public CPage(int id)
      : base(id)
    {
      this.initializeComponents();
    }

    public CPage(int id, bool forgetable, bool clearHistory )
      : base(id)
    {
      _forgetable = forgetable;
      _clearHistory = clearHistory;

      this.initializeComponents();
    }

	  public CPage(int id, string name)
      : base(id)
    {
      this.initializeComponents();
      this.mText = name;
    }

    //////////////////////////////////////////////////////////////////////////////////
    protected virtual void initializeComponents()
    {
      _controller.insertPage(this);
    }
    
    public override void onDraw(CObserver renderer)
    {
      base.onDraw(renderer);
	    if (mText.Length > 0)
	    {
		    renderer.drawText(0,  _controller.translate(mText), EAlignment.justify_left);
	    }
    }

    public override void onErase(CObserver renderer)
    {
	    base.onErase(renderer);
	    if (mText.Length > 0)
	    {
		    renderer.eraseText(0,  _controller.translate(mText), EAlignment.justify_left);
	    }    
    }

    public override bool onEscKey()
    {
      _controller.previousPage();
      return true;
    }
    
    /**
    * Check if this page can be saved to history.
    * @return true if this page is not for history.
    */
    public bool isForgetable() { return _forgetable; }
		
	  /**
	  * Clear history on entry
	  *
	  */
	  public bool isClearHistory() { return _clearHistory; }

    /**
    * Set flag to NOT save this page in history.
    */
    public void forgetPage(bool value) { _forgetable = value; }

	  /**
	  * Set flag history flag.
	  */
	  public void clearHistory(bool value) { _clearHistory = value; }

	  /**
	  * Set title text
	  */
	  public void setText(string text) { mText = text; }
  }

  /**
   * @class CText
   *
   * @brief Elementary component used to display text on screen.
   *
   * @author Sasa Coh
   */
  public class CText : CComponent
  {
    #region Variables

    protected string _caption;

    private EAlignment _alignment = EAlignment.justify_left;

    private int _posX = 0;

    #endregion Variables

    #region Properties

    public string Caption 
    {
      set {
        //_controller.eraseComponent(this);
        _caption = value;
      }
      get { return _caption; }
    }

    public EAlignment Align
    {
      set { _alignment = value; }
      get { return _alignment;  }
    }

    #endregion Properties

    public CText(string caption, EAlignment alignment)
      : base(0)
    {
      _caption = caption;
      Align = alignment;
    }

    public CText(string caption)
      : base(0)
    {
      _caption = caption;
    }

    public override void onDraw(CObserver renderer)
    {
      if (this.PosY < 0) return;
      if (PosX > 0)
        renderer.drawText(PosX, PosY, _controller.translate(_caption));
      else
        renderer.drawText(PosY, _controller.translate(_caption), this._alignment);
    }

    public override void onErase(CObserver renderer)
    {
      if (this.PosY < 0) return;
      if (PosX > 0)
        renderer.eraseText(PosX, PosY, _controller.translate(_caption));
      else
        renderer.eraseText(PosY, _controller.translate(_caption), this._alignment);
    }

  }

  /**
   * @class CLink
   *
   * @brief Elementary component used for browsing and other UI actions. 
   *
   * Handles soft-key button press.
   *
   * @author Sasa Coh
   */
  public class CLink : CText 
  {
    protected int mpageId;

    public CLink(string caption)
      : this(caption, -1)
    { }

    public CLink(string caption, int pageId)
      : base(caption)
    {
      this.Softkey += new UintDelegate(handleSoftkey);
      this.mpageId = pageId;
    }
    
    public override void onDraw(CObserver renderer)
    {
      base.onDraw(renderer);
      //renderer.drawLink(this.Pos, _controller.translate(_caption));
    }

    public override void onErase(CObserver renderer)
    {
      base.onErase(renderer);
      //renderer.eraseLink(this.Pos, _controller.translate(_caption));
    }

    public override bool onSoftKey(int id)
    {
      if (this.LinkKey == id)
      {
        return base.onSoftKey(id);
      }
      else
        return false;
    }

    public bool handleSoftkey(int id)
    {
      if (this.LinkKey == id)
      {
        _controller.showPage(mpageId);
        return true;
      }
      return false;
    }
  }

  /**
   * @class CEditField
   *
   * @brief Component used to handle user input. 
   *
   * @author Sasa Coh
   */
  public class CEditField : CLink 
  {
    protected EEditMode _entryMode = EEditMode.alphanum_low;
    protected EEditMode _currentMode = EEditMode.alphanum_low;

    protected int _last_key = -1;
    protected int _cursor_position = 0;
    protected int _inter_cnt = 0;

    protected string _prompt;
    protected bool _initialSelected = false;
    protected char mchReplacement;
    protected int _interkey_time = 2000;
    protected CComponentTimer _interkey_timer;

    public bool DefaultFocus 
    {
      set { _initialSelected = value; }
    }

    public CEditField(string prompt, string caption)
      : this(prompt, caption, EEditMode.alphanum_low,false)
    {
    }

    public CEditField(string prompt, string caption, bool focused)
      : this(prompt, caption, EEditMode.alphanum_low, focused)
    {
    }

    public CEditField(string prompt, string caption, EEditMode entryMode)
      : this(prompt, caption, entryMode, false)
    {
    }

    public CEditField(string prompt, string caption, EEditMode entry_mode, bool focused)
      : base(prompt + caption)
    {
      _prompt = prompt;
      _caption = caption;
      _entryMode = entry_mode;
      _currentMode = entry_mode;
      _initialSelected = focused;
      _interkey_timer = _controller.createTimer(onInterkeyTimeout);
    }

    public void setTextReplacementChar(char ch)
    { 
      mchReplacement = ch;
    }

    public override void onDraw(CObserver renderer)
    { 
      	string text = _caption;

        if (this.mchReplacement != 0)
	      {
          text = new string(this.mchReplacement, _caption.Length);
	      }
	      renderer.drawEdit(PosY, _controller.translate(_prompt), text, _cursor_position, this.Focused, _currentMode);
    }

    public override void onErase(CObserver renderer)
    {
      renderer.eraseEdit(PosY, _controller.translate(_prompt), _caption, _cursor_position);
    }

    public override void onEntry()
    {
      _last_key = -1;
      _inter_cnt = 0;
      _cursor_position = _caption.Length;
      Focused = _initialSelected;
      base.onEntry();
    }

    public override void onExit()
    {
      Focused = false;
      base.onExit();
    }

    public override bool onDigitKey(int id)
    {
      _interkey_timer.abort();
      // check if selected
      if (!this.Focused) return false;

      // Change edit entry mode
      if (id == (int)ENumKeyTags.NumKey_hash)
      {
		    _interkey_timer.abort();
        if (_last_key != -1)
        {
          // If Alphanumeric entry mode, increase cursor position.
          if (_currentMode != EEditMode.numeric)
          {
            _cursor_position++;
          }
          _last_key = -1;
          _inter_cnt = 0;
        }
        toggleEditMode();	// toggle mode
		    _controller.drawComponent(this);
        return true;
      } 
      // Change case entry mode
      else if (id == (int)ENumKeyTags.NumKey_star)
      {
        toggleEditCase();	// toggle case
		    _controller.drawComponent(this);
        return true;
      }

      // reset inter counter in case of digit change
      if (id != _last_key)
      {
        _inter_cnt = 0;
      }
      // Get alphanumeric character
      string character = _controller.getCharacter(id, _inter_cnt, _currentMode).ToString();
	          
      // TRUE - Alphanumeric, FALSE - Numeric
      if (_entryMode != EEditMode.numeric/*_alpha_num->isAlphanumericMode()*/)
      {
        // New alphanumeric character
        if (_last_key == -1)
        {
          _caption = _caption.Insert(_cursor_position, character);
          _interkey_timer.startMillis(_interkey_time);
        } 
        // Next alphanumeric character on the same key
        else if (id == _last_key)
        {
          _caption = _caption.Remove(_cursor_position, 1);
          _caption = _caption.Insert(_cursor_position, character);
          _interkey_timer.startMillis(_interkey_time);
        } 
        // New alphanumeric character before interkey timer timeout
        else if (_last_key != id)
        {
          _cursor_position++;
          _caption = _caption.Insert(_cursor_position, character);          
          _interkey_timer.startMillis(_interkey_time);
        }
      }
      else
      {
        // New numeric character
        _caption = _caption.Insert(_cursor_position, character);
        _cursor_position++;
      }
      // Remember last pressed key.
      _last_key = (int)id;
      _controller.drawComponent(this);
	
	    _inter_cnt ++;

	    return base.onDigitKey(id);    
    }

    public override bool onClearKey()
    {
      // check if selected
      if (!this.Focused) return false;

      _interkey_timer.abort();

      // Erase only if cursor position biger  than 0.
      if (_cursor_position > 0)
      {
        _controller.eraseComponent(this);
        // if(_last_key != -1) interkey timer ON or Numeric entry mode.
        if (_last_key != -1)
        {
          // If Numeric entry mode, decrease cursor position.
          if (_currentMode == EEditMode.numeric)
          {
            _cursor_position--;
          }
          _last_key = -1;
          _inter_cnt = 0;
        }
        else
        {
          _cursor_position--;
        }
        _caption = _caption.Remove(_cursor_position, 1);
        _controller.drawComponent(this);
      } 
      // If 0 cursor position and interkey timer ON, delete 0 position.
      else if (_cursor_position == 0 && _last_key != -1)
      {
        _controller.eraseComponent(this);
        _last_key = -1;
        _caption = _caption.Remove(_cursor_position, 1);
        _controller.drawComponent(this);
      }
	    _inter_cnt = 0;
	    return base.onClearKey();
    }

    public override bool onLeftKey()
    {
      return true;
    }
    public override bool onRightKey()
    {
      return true;
    }

    public override bool onSoftKey(int id) 
    {
      if (id == this.LinkKey)
      {
        Focused = true;
        //show status for this component(if needed)!
        _controller.drawComponent(this);
        return true;
      }
      return false;
    }

    protected bool onInterkeyTimeout()
    {
      // check if selected
      if (!Focused) return false;

      _inter_cnt = 0;
      _last_key = -1;

      if (_cursor_position < _caption.Length) _cursor_position++;
      
      _controller.drawComponent(this);
      return true;
    }

    protected void toggleEditMode()
    {
      if (_entryMode == EEditMode.numeric) return; // no change for numeric fields

      if ((_currentMode == EEditMode.alphanum_high) || (_currentMode == EEditMode.alphanum_low))
        _currentMode = EEditMode.numeric;
      else
        _currentMode = EEditMode.alphanum_low;
    }

    protected void toggleEditCase()
    {
      if (_entryMode == EEditMode.numeric) return; // no change for numeric fields

      if (_currentMode == EEditMode.alphanum_high)
        _currentMode = EEditMode.alphanum_low;
      else
        _currentMode = EEditMode.alphanum_high;
    }
  }

  /**
   * @class CCheckBox
   *
   * @brief CCheckBox component adds check box facility. 
   *
   * @author Sasa Coh
   */
  public class CCheckBox :  CLink 
  {
    private bool _checked;

    public bool Checked 
    {
      set { 
        _checked = value;
        if ((_checked) && (OnChecked != null)) 
          OnChecked();
        else
          if (OnUnchecked != null) 
            OnUnchecked();

        _controller.drawComponent(this);
      }
      get { return _checked; }
    }

    public CCheckBox(string caption)
      : this(caption, 0)
    { }

    public CCheckBox(string caption, int pageId)
      : this(caption, pageId, false)
    { }

    public CCheckBox(string caption, int pageId, bool checkedFlag)
      : base(caption, pageId)
    {
      this._checked = checkedFlag;
    }

    public override void onDraw(CObserver renderer)
    {
      renderer.drawCheckMark(_caption.Length, PosY, Checked);
      base.onDraw(renderer);
    }

    public override void onErase(CObserver renderer)
    {
      renderer.eraseCheckMark(_caption.Length, PosY);
      base.onErase(renderer);
    }

    public override bool onSoftKey(int id)
    {
      if (id == this.LinkKey) Checked = !Checked;
      return true;
    }

    public event NoParamDelegate OnChecked;	// OnChecked handler...
    public event NoParamDelegate OnUnchecked;	// OnUnchecked handler...
  
  }

  /**
   * @class CIpAddressEdit
   *
   * @brief Handles user input for Ip address
   *
   * @author Sasa Coh
   */
  public class CIpAddressEdit : CEditField
  {
    public CIpAddressEdit(string prompt)
      : this(prompt, "000.000.000.000")
    { }

    public CIpAddressEdit(string prompt, string ipAddress)
      : base(prompt,ipAddress, EEditMode.numeric)
    {
    }

    public new string Caption
    {
      set { _caption = format(_caption); }
      get { return normalize(_caption); }
    }

    public override bool onDigitKey(int id)
    {
      // check if selected
      if (!this.Focused) { return false; }

      // check digit code...
      if ((id >= (int)ENumKeyTags.NumKey_0) && (id <= (int)ENumKeyTags.NumKey_9))
      {     
        //character .Assign(_controller.getKeyCharacter(id), 1);
        string character = _controller.getCharacter(id, 0, _entryMode).ToString();
        _caption = _caption.Remove(_cursor_position,1);
        _caption = _caption.Insert(_cursor_position, character );
        // check cursor position
        if (_cursor_position < 14)
        {
          _cursor_position++;
          // jump over '.'
          if (((_cursor_position + 1) % 4) == 0)
          {
            _cursor_position++;
          }
        }
        _last_key = (int)id;

        _controller.drawComponent(this);
      }
      return true;
    }

    public override bool onLeftKey()
    {
      // check if selected
      if (!this.Focused) { return false; }

      // TODO
      _last_key = -1;
      if (_cursor_position > 0)
      {
        _controller.eraseComponent(this);
        _cursor_position--;
        // jump over '.'  
        if ((((_cursor_position + 1) % 4) == 0) && (_cursor_position > 0))
        {
          _cursor_position--;
        }

        _controller.drawComponent(this);
      }
      return true;
    }

    public override bool onRightKey()
    {
      // check if selected
      if (!this.Focused) { return false; }

      _last_key = -1;
      if (_cursor_position >= 0)
      {
        _controller.eraseComponent(this);
        if (_cursor_position < 14)
        {
          _cursor_position++;
          // jump over '.'  
          if (((_cursor_position + 1) % 4) == 0)
          {
            _cursor_position++;
          }
        }
        _controller.drawComponent(this);
      }
      return true;
    }

    public override bool onClearKey()
    {
      // check if selected
      if (!this.Focused) { return false; }

      //---------------------------------------
      if (_cursor_position >= 0)
      {
        _controller.eraseComponent(this);

        //_caption[_cursor_position] = '0';
        _caption = _caption.Remove(_cursor_position, 1);
        _caption = _caption.Insert(_cursor_position, "0");

        if (_cursor_position > 0)
        {
          _cursor_position--;
        }

        // jump over '.'  
        if ((((_cursor_position + 1) % 4) == 0) && (_cursor_position > 0))
        {
          _cursor_position--;
        }

        _controller.drawComponent(this);
      }
      return true;
    }

    public override void onEntry()
    {
      base.onEntry();
      _cursor_position = 0;
    }

    protected string format(string ipAddress)
    {
      string normIp = ""; 
      int position = 0, oldPosition = 0;
      int length = ipAddress.Length;
      int partCnt = 0;

	    // return default Ip address if ""
	    if (0 == length) {
		    return "000.000.000.000";
	    }

      while (position < length)
      {
        position = ipAddress.IndexOf('.', oldPosition);
        if (position == 0)
        {
          position = ipAddress.Length;
        }

        if ((position != oldPosition) && (oldPosition < position))
        {
          partCnt++;
          string part = ipAddress.Substring(oldPosition, (position - oldPosition));
          if (part.Length == 2)
          {
            part = "0" + part;
          }
          if (part.Length == 1)
          {
            part = "00" + part;
          }
          normIp += part;
          if (partCnt < 4)
          {
            normIp += ".";
          }
        }
        oldPosition = position + 1;
      }
      return normIp;
    }
     
    protected string normalize(string ipAddress)
    {
      string normIp = "";
      int position = 0, oldPosition = 0;
      int length = ipAddress.Length;
      int partCnt = 0;

      while (position < length)
      {
        position = ipAddress.IndexOf('.', oldPosition);
        if (position == 0)
        {
          position = ipAddress.Length;
        }

        if ((position != oldPosition) && (oldPosition < position))
        {
          partCnt++;
          string part = ipAddress.Substring(oldPosition, (position - oldPosition));
          if (part[0] == '0')
          {
            part.Remove(0, 1);
            if (part[0] == '0')
            {
              part.Remove(0, 1);
            }
          }
          normIp += part;
          if (partCnt < 4)
          {
            normIp += ".";
          }
        }
        oldPosition = position + 1;
      }

      return normIp;
    }
  }

  #region Decorators

  /**
  * @class CVerticalScrollDecorator
  *
  * @brief Vertical decorator is used to add vertical scrolling facility for component list. 
  *
  * ScrollDecorator recalculates position parameter of all listed components.
  * At creation you can set position of scrollbar and index of initial element.
  * This vertical decorator is appropriate for devices with side soft keys.
  *
  * @author Sasa Coh
  */
  public class CVerticalScrollDecorator : CDecorator 
  {

    protected int _listPos = 0;
    protected int _winSize;
    protected bool _cycle = false;

    public CVerticalScrollDecorator(CComposite component,
                                    int winSize,
                                    bool cycle
                                    )
      : base(component)
    {
      this._cycle = cycle;
      this._winSize = winSize;
    }

    protected void initScrollList()
    {
      CComposite cps = (CComposite)_component;
      int cnt = 0;
      foreach (CComponent comp in cps.getList())
      {
        comp.PosY = PosY + this._listPos + cnt;
        cnt++;
      }
    }

    public override void onDraw(CObserver renderer)
    {
      CComposite cps = (CComposite)_component;
      foreach (CComponent comp in cps.getList())
      {
        if (comp.PosY >= this.PosY && comp.PosY < (this.PosY + _winSize))
        {
          comp.onDraw(renderer);
        }
      }
    }

    public override void onErase(CObserver renderer)
    {
      CComposite cps = (CComposite)_component;
      foreach (CComponent comp in cps.getList())
      {
        if (comp.PosY >= this.PosY && comp.PosY < (this.PosY + _winSize))
        {
          comp.onErase(renderer);
        }
      }
    }

    public override void onEntry()
    {
      this.initScrollList();
      base.onEntry();
    }

    public override bool onUpKey()
    {
      int limit = _winSize;
      if (_winSize > _component.Size) limit = _component.Size;

      _controller.eraseComponent(this);

      CComposite cps = (CComposite)_component;

      int cnt = 0;
      foreach (CComponent comp in cps.getList())
      {
        cnt++;
        comp.PosY = comp.PosY + 1;
        if (comp.PosY >= (limit + _component.PosY))
        {
          comp.PosY = comp.PosY % cps.Size;
        }
      }
      _controller.drawComponent(this);
      return true;
    }

    public override bool onDownKey()
    {
      _controller.eraseComponent(this);

      CComposite cps = (CComposite)_component;

      foreach (CComponent comp in cps.getList())
      {
        //if (!_cycle)
        comp.PosY = comp.PosY - 1;
        if (comp.PosY < _component.PosY)
        {
          comp.PosY = comp.PosY + cps.Size;
        }
      }
      _controller.drawComponent(this);
      return true;
    }

    public override bool onLeftKey()
    {
      return this.onUpKey();
    }

    public override bool onRightKey()
    {
      return this.onDownKey();
    }

    // Warning: LinkKeys are mixed due to scrolling!!! 
    // Compare item position and softkey id when invoking softkey handler.
    public override bool onSoftKey(int id)
    {
      // todo:::find item in list
      CComposite cps = (CComposite)_component;
      
      foreach (CComponent comp in cps.getList())
      {
        if (comp.PosY == id)
        {
          return comp.onSoftKey(id);         
        }
      }
      return true;
    }

    #region Implementation

    public CComponent getItem(int ind)
    {
      CComposite cps = (CComposite)_component;
      foreach (CComponent comp in cps.getList())
      {
        if (comp.PosY == ind + _component.PosY) return comp;
      }
      return CNullComponent.instance();
    }
    #endregion
  }

  /// <summary>
  /// 
  /// </summary>
  public class CSelectionDecorator : CDecorator
  {
    private int _selection;
    public int Selection
    {
      get { return _selection; }
      set { 
        _controller.eraseComponent(this);
        _selection = value;
        _controller.drawComponent(this);
      }
    }

    private int _size;

    public CSelectionDecorator(CComponent component, int size)
      : base(component)
    {
      _size = size;
      _selection = 0;
    }

    public override void onDraw(CObserver renderer)
    {
      base.onDraw(renderer);
      if ((_selection < _size)&&(_component.Size > 0)) renderer.drawSelection(_selection + _component.PosY, 0);
    }

    public override void onErase(CObserver renderer)
    {
      if ((_selection < _size)&&(_component.Size > 0)) renderer.eraseSelection(_selection + _component.PosY, 0);
      base.onErase(renderer);
    }

    public override bool onSoftKey(int id)
    {
      if ((id - _component.PosY >= 0) && (id - _component.PosY < _size))
        Selection = id - _component.PosY;

      // set selection
      return base.onSoftKey(id);
    }

    public override bool onOkKey()
    {
      // find selected item

      return base.onOkKey();
    }

    public override bool onUpKey()
    {
      _controller.eraseComponent(this);
      if (_selection < 1)
      {
        _component.onUpKey();
      }
      else
      {
        _selection--;
      }

      _controller.drawComponent(this); 
      return true;
    }

    public override bool onDownKey()
    {
      int limit = _size;
      _controller.eraseComponent(this);

      if (_size > _component.Size) limit = _component.Size;

      if (_selection >= limit - 1)
      {
        _component.onDownKey();
      }
      else
      {
        _selection++;
      }

      _controller.drawComponent(this);
      return true;
    }

    public override bool onLeftKey()
    {
      return this.onUpKey();
    }

    public override bool onRightKey()
    {
      return this.onDownKey();
    }

  }

  /// <summary>
  /// 
  /// </summary>
  public class CTimeoutDecorator : CDecorator
  {
    private CComponentTimer _timer;
    private int _timeout;
    public event NoParamDelegate OnTimeout;	// timeout handler...

    public CTimeoutDecorator(CComponent comp, int timeout)
      : base(comp)
    {
      _timer = _controller.createTimer(new NoParamDelegate(timeoutHandler));
      _timeout = timeout;
    }

    public override void onEntry()
    {
      _timer.startMillis(_timeout);
      base.onEntry();
    }

    public override void onExit()
    {
      _timer.abort();
      base.onExit();
    }

    public bool timeoutHandler()
    {
      if (OnTimeout != null) return OnTimeout();
      return false;
    }

  }

  /// <summary>
  /// 
  /// </summary>
  public class CHorizontalFlow : CDecorator
  {
    private CComponentTimer _timer;
    private int _timeout = 500;
    private CText _text;
    private int _window;

    public CHorizontalFlow(CText comp, int window)
      : this(comp, window, 500)
    {}

    public CHorizontalFlow(CText comp, int window, int timeout)
      : base(comp)
    {
      _text = comp;
      _window = window;
      _timeout = timeout;
      _timer = _controller.createTimer(new NoParamDelegate(handleTimeout));
    }

    public override void onEntry()
    {
      _timer.startMillis(_timeout);
      base.onEntry();
    }

    public override void onExit()
    {
      _timer.abort();
      base.onExit();
    }

    private bool handleTimeout()
    {
      string temp = _text.Caption.Substring(1);
      temp += _text.Caption[0];
      _text.Caption = temp;
      _timer.startMillis(_timeout);
      _controller.refresh();
      return true;
    }

  }


  #endregion Decorators

  #region Composites

  /**
   * @class CComponentList
   *
   * @brief CComponentList class template
   *
   * @author Sasa Coh
   */
  public class CComponentList : CComposite
  {
    public CComponentList()
      : base(0)
    { }
  }


  /// <summary>
  /// CRadioButtonGroup handles group of checkboxes where only one item can be selected  
  /// </summary>
  public class CRadioButtonGroup : CComposite
  {
    public CRadioButtonGroup()
      : base(0)
    { }

    public override void add(CComponent component)
    {
      if (component.GetType() != typeof(CCheckBox)) return;
      base.add(component);
    }

    public override bool onSoftKey(int id)
    {
      foreach (CCheckBox comp in _components)
      {
        if (id != comp.LinkKey)
          comp.Checked = false;
        else
          if (!comp.Checked) comp.onSoftKey(id);
      }
      return true;
    }
  }


  /// <summary>
  /// 
  /// </summary>
  public class CSelectList : CDecorator
  {
    private CSelectionDecorator _selDecor;
    private CVerticalScrollDecorator _vertDecor;
    private CComponentList _list;

    public CComponent Selected
    {
      get { return _vertDecor.getItem(_selDecor.Selection); }
    }

    public CSelectList(CComponentList list, int winSize)
      : base(null)
    {
      _list = list;
      // create decorators
      _vertDecor = new CVerticalScrollDecorator(_list, winSize, false);
      _selDecor = new CSelectionDecorator(_vertDecor, winSize);
      _component = _selDecor;
    }

    public override bool onOkKey()
    {
      return Selected.onOkKey();
    }

    public override bool onSoftKey(int id)
    {
      // todo
      return base.onSoftKey(id);
    }

  }

  #endregion Composites

} // namespace UIFramework
